﻿using System;
using System.Collections.ObjectModel;
using System.Reflection;
using System.Linq.Expressions;

namespace Pfz.ErrorHandling
{
	/// <summary>
	/// Class that groups errors instead of letting the first one throw an exception.
	/// It has methods to start the packing (and disposing it stops the packing),
	/// methods to add errors, remove and clear errors (in case you know how to deal
	/// with them).
	/// </summary>
	public static class ErrorPack
	{
		[ThreadStatic]
		internal static _ErrorPack _current;
		
		/// <summary>
		/// Enables the packing if it is not yet enabled.
		/// Use this with the using clause, so at the end the packing is disabled
		/// (and an exception can be thrown if there are errors).
		/// Note: If EnablePacking is nested, only the outer one generates
		/// exceptions. This is on purpose, as small methods may want to pack
		/// some errors, but outer methods may want to pack all the errors of their
		/// inner methods.
		/// </summary>
		public static IDisposable EnablePacking()
		{
			if (_current != null)
				return null;
				
			var result = new _DefaultErrorPack();
			return result;
		}
		
		/// <summary>
		/// Adds an error to the error pack.
		/// </summary>
		public static void Add(object error)
		{
			if (error == null)
				throw new ArgumentNullException("error");
				
			var pack = _current;
			if (pack == null)
				throw new ErrorPackException(error);
				
			pack._actualErrorPack.Add(error);
		}


		/// <summary>
		/// Adds a PropertyError object to the error list.
		/// </summary>
		public static void Add(PropertyInfo property, object error)
		{
			using(new PropertyErrorPack(property))
				Add(error);
		}

		/// <summary>
		/// Adds a PropertyError object to the error list.
		/// Uses an expression to know what propertyInfo to use.
		/// </summary>
		public static void Add<T>(Expression<Func<T>> getPropertyExpression, object error)
		{
			var propertyInfo = ReflectionHelper.GetProperty(getPropertyExpression);
			Add(propertyInfo, error);
		}
		
		/// <summary>
		/// Clears all the errors in the pack and disables the error packing.
		/// It is highly recommended that after dealing with the errors you
		/// call Clear and then dispose the error pack.
		/// </summary>
		public static void Clear()
		{
			var pack = _current;
			if (pack != null)
			{
				pack._baseErrorPack._errors.Clear();
				_current = null;
			}
		}
		
		private static readonly ReadOnlyCollection<object> _emptyErrors = new ReadOnlyCollection<object>(new object[0]);
		/// <summary>
		/// Gets a collection with all the errors.
		/// If there are no errors, an empty collection is returned.
		/// </summary>
		public static ReadOnlyCollection<object> Errors
		{
			get
			{
				var pack = _current;
				if (pack == null)
					return _emptyErrors;
					
				return pack._baseErrorPack._roErrors;
			}
		}
		
		/// <summary>
		/// Gets a value indicating if there are errors packed.
		/// </summary>
		public static bool HasErrors
		{
			get
			{
				var pack = _current;
				if (pack == null)
					return false;
					
				return pack._baseErrorPack._errors.Count > 0;
			}
		}
	}
}
